﻿using System;
using LinqToAmazonSL.AWS;
using System.Linq.Expressions;

namespace LinqToAmazonSL
{
    public class AmazonQueryProvider<T> : QueryProvider where T : AmazonItem
    {
        public AmazonQueryCriteria Criteria { get; set; }
        private AmazonHelper amazonHelper;

        public AmazonQueryProvider(AmazonQueryCriteria criteria, string amazonKey)
        {
            this.Criteria = criteria;
            this.amazonHelper = new AmazonHelper() {};
            this.amazonHelper.SetAmazonKey(amazonKey);
        }

        public bool ValidateKey()
        {
            return amazonHelper.ValidateAmazonKey();
        }


        public override ItemSearch GetAmazonQuery(Expression expression)
        {
            // Retrieve criteria
            Criteria = new AmazonExpressionVisitor(Criteria).ProcessExpression(expression);

            // Generate an ItemSearch which is the way to do a request to the 
            // Amazon Web Service.
            ItemSearch itemSearch = amazonHelper.BuildRequest(Criteria);

            return itemSearch;
        }

        public override object Execute(Expression expression)
        {
            return null;
        }

        public override object Execute<S>(Expression expression)
        {
            ItemSearch request = GetAmazonQuery(expression);

            object result = null;

            if (Criteria.OperationsStack.Count == 0)
            {
                //If there are no operations concatenated
                if (Criteria.Selector != null)
                {
                    //If the "Query" is consulted by a particular field
                    result = amazonHelper.PerformWebQuery<T, S>(request, Criteria.Selector);
                }
                else
                    result = amazonHelper.PerformWebQuery<T>(request);
            }
            else
            {
                //Get the latest operation applied to the "Query"
                Operations firstOperation = (Criteria.OperationsStack.ToArray())[Criteria.OperationsStack.Count - 1];
                GuideRequest dr = new GuideRequest();

                //Process to "dr" by applying the operations contained in the "Stack"
                foreach (var op in Criteria.OperationsStack)
                {
                    dr = OperationPerformer.DoOperation(op, dr);
                }

                //Considering the last operation applied to the "Query", to invoke a "PerformXXXQuery" specific to this operation
                switch (dr.LastOperation)
                {
                    case Oper.Count:
                        result = amazonHelper.PerformCountQuery(request, dr);
                        break;
                    case Oper.First:
                        result = amazonHelper.PerformWebQuery<T>(request, Oper.First, dr, Criteria.Selector);
                        break;
                    case Oper.FirstOrDefault:
                        result = amazonHelper.PerformWebQuery<T>(request, Oper.FirstOrDefault, dr, Criteria.Selector);
                        break;
                    case Oper.Last:
                        result = amazonHelper.PerformWebQuery<T>(request, Oper.Last, dr, Criteria.Selector);
                        break;
                    case Oper.LastOrDefault:
                        result = amazonHelper.PerformWebQuery<T>(request, Oper.LastOrDefault, dr, Criteria.Selector);
                        break;
                    case Oper.ElementAt:
                        result = amazonHelper.PerformWebQuery<T>(request, Oper.ElementAt, firstOperation.Value, dr, Criteria.Selector);
                        break;
                    case Oper.ElementAtOrDefault:
                        result = amazonHelper.PerformWebQuery<T>(request, Oper.ElementAtOrDefault, firstOperation.Value, dr, Criteria.Selector);
                        break;
                    case Oper.Any:
                        result = amazonHelper.PerformBoolQuery(request, dr);
                        break;
                    case Oper.Skip:
                        if (Criteria.Selector != null)
                            result = amazonHelper.PerformWebQueryCollection<T, S>(request, Oper.Skip, firstOperation.Value, dr, Criteria.Selector);
                        else
                            result = amazonHelper.PerformWebQueryCollection<T>(request, Oper.Skip, firstOperation.Value, dr);
                        break;
                    case Oper.Take:
                        if (Criteria.Selector != null)
                            result = amazonHelper.PerformWebQueryCollection<T, S>(request, Oper.Take, firstOperation.Value, dr, Criteria.Selector);
                        else
                            result = amazonHelper.PerformWebQueryCollection<T>(request, Oper.Take, firstOperation.Value, dr);
                        break;
                    default:
                        throw new InvalidOperationException("unsupported operation");
                }
            }

            //Clean the search criteria
            this.Criteria = Criteria.ClearCriteria();
            return result;
        }
    }
}
